﻿using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Text;
using Microsoft.Win32;
using System.Reflection;
using Microsoft.SharePoint.Client;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Forms;


namespace Sam.Craven
{
    
    #region IObjectSafety Interface Declaration


    /// <summary>
    /// SharePoint 2010 OpenDocument interface for PDF Files.
    /// Adobe PDF Reader is required for operation for this .Net Com class.
    /// This class is registered using the current user hive not local machine hive--does not 
    /// need admin rights to install.
    /// Author, Simon T Baker, 5/14/2014
    /// Interface copied from the Microsoft Forum link to Malag.SharePoint.OpenDocuments
    /// 
    /// </summary>
    [ComImport]
    [Guid("CB5BDC81-93C1-11CF-8F20-00805F2CD064")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]    interface IObjectSafety
    {
        [PreserveSig]
        int GetInterfaceSafetyOptions(ref Guid riid, out int pdwSupportedOptions, out int pdwEnabledOptions);

        [PreserveSig]
        int SetInterfaceSafetyOptions(ref Guid riid, int dwOptionSetMask, int dwEnabledOptions);
    }
    #endregion

    #region IOpenDocuments Interface Declaration

   
    [ComVisible(true)]
    [InterfaceType(ComInterfaceType.InterfaceIsDual)]
    public interface IOpenDocument
    {

        [DispIdAttribute(0x60020001)]
        bool CreateNewDocument([In, MarshalAs(UnmanagedType.BStr)] string bstrTemplateLocation, [In, MarshalAs(UnmanagedType.BStr)] string bstrDefaultSaveLocation);

        [DispIdAttribute(0x60020002)]
        bool CreateNewDocument2([In, MarshalAs(UnmanagedType.IDispatch)] object pdisp, [In, MarshalAs(UnmanagedType.BStr)] string bstrTemplateLocation, [In, MarshalAs(UnmanagedType.BStr)] string bstrDefaultSaveLocation);

        [DispIdAttribute(0x60020003)]
        bool EditDocument([In, MarshalAs(UnmanagedType.BStr)] string bstrDocumentLocation, [In, MarshalAs(UnmanagedType.BStr)] string varProgID);

        [DispIdAttribute(0x60020004)]
        bool EditDocument2([In, MarshalAs(UnmanagedType.IDispatch)] object pdisp, [In, MarshalAs(UnmanagedType.BStr)] string bstrDocumentLocation, [In, MarshalAs(UnmanagedType.BStr)] string varProgID);

        [DispIdAttribute(0x60020005)]
        bool PromptedOnLastOpen();

        [DispIdAttribute(0x60020006)]
        bool ViewDocument([In, MarshalAs(UnmanagedType.BStr)] string bstrDocumentLocation, [In, MarshalAs(UnmanagedType.BStr)] string varProgID);

        [DispIdAttribute(0x60020007)]
        bool ViewDocument2([In, MarshalAs(UnmanagedType.IDispatch)] object pdisp, [In, MarshalAs(UnmanagedType.BStr)] string bstrDocumentLocation, [In, MarshalAs(UnmanagedType.BStr)] string varProgID);

    }
    #endregion


    /// <summary>
    /// Supports document interface 2. Can support opendocument 1.
    /// Just uncomment Sam.Craven.OpenDocuments.1 and comment out Sam.Craven.OpenDocuments.2
    /// 
    /// </summary>
    //[ProgId("Sam.Craven.OpenDocuments.1")]
    [ProgId("Sam.Craven.OpenDocuments.2")]
    [Guid("C666BAE7-23D2-47bc-A283-CB90CD23FBBC")]
    [ClassInterface(ClassInterfaceType.None)]
    [ComVisible(true)]
    [ComDefaultInterface(typeof(IOpenDocument))]
    public sealed class OpenDocuments : IOpenDocument, IObjectSafety 
    {

        
        #region IObjectSafety Implementation
        /// <summary>
        /// Gets the safety options supported by an object and the safety options that are currently set for that object.
        /// </summary>
        /// <param name="riid">An interface identifier for a given object.</param>
        /// <param name="pdwSupportedOptions">Returns that the caller of the interface 
        /// and the data passed into the interface identified by riid might be untrusted.</param>
        /// <param name="pdwEnabledOptions">Identifies these pdwSupportedOptions as currently enabled for the interface identified by riid.</param>
        /// <returns></returns>
        public int GetInterfaceSafetyOptions(ref Guid riid, out int pdwSupportedOptions, out int pdwEnabledOptions)
        {
            // Constants for implementation of the IObjectSafety interface.
            // INTERFACESAFE_FOR_UNTRUSTED_CALLER = 0x00000001
            // INTERFACESAFE_FOR_UNTRUSTED_DATA = 0x00000002
            // S_OK = 0

            pdwEnabledOptions = 0x00000001 | 0x00000002;
            pdwSupportedOptions = 0x00000001 | 0x00000002;
            return 0;
        }

        /// <summary>
        /// Always returns S_OK to indicate that the object is safe for initialization and scripting.
        /// </summary>
        /// <param name="riid"></param>
        /// <param name="dwOptionSetMask"></param>
        /// <param name="dwEnabledOptions"></param>
        /// <returns></returns>
        public int SetInterfaceSafetyOptions(ref Guid riid, int dwOptionSetMask, int dwEnabledOptions)
        {
            
            return 0;
        }
        #endregion

        public OpenDocuments() { }

        #region IOpenDocuments Members
        
        
        /// <summary>
        /// Method for opendocuments.1 Copies the PDF template from bstrTemplateLocation to bstrDefaultSaveLocation
        /// so that it can be opened with Adobe
        /// </summary>
        /// <param name="bstrTemplateLocation"></param>
        /// <param name="bstrDefaultSaveLocation"></param>
        /// <returns></returns>
        [ComVisible(true)]
        [DispId((0x60020001))]
        bool IOpenDocument.CreateNewDocument(string bstrTemplateLocation, string bstrDefaultSaveLocation)
        {

            try
            {
                
                //find the UNC path to the template and saved template
                string uncPath = bstrDefaultSaveLocation.Replace('/', '\\').Remove(0, 5);
                string uncPDFTemplateFullPath = bstrTemplateLocation.Replace('/', '\\').Remove(0, 5);

                string uncPDFNewFileName = System.IO.Path.GetFileNameWithoutExtension(uncPDFTemplateFullPath);
                string uncContentTemplateName = System.IO.Path.GetFileName(uncPDFTemplateFullPath);
                //Create new PDF file name for the saved file. Adobe PDF reader will not create a new file.
                uncPDFNewFileName = string.Format(@"{0} {1} {2}.pdf", uncPDFNewFileName,
                    System.DateTime.Now.ToString("yyyymmddhhmm"), Environment.UserName);

                string uncPDFNewFileNameFullPath = string.Format(@"{0}\{1}", uncPath, uncPDFNewFileName);
                //copy template
                
                CopyFile(uncPDFTemplateFullPath, uncPDFNewFileNameFullPath);
                string bstrNewFileFullPath = "http:" + uncPDFNewFileNameFullPath.Replace('\\', '/');
                string library = string.Empty;
                string bstrNewDocName = string.Empty;
                string webURL = ReturnWebSiteURL(bstrNewFileFullPath, out library, out bstrNewDocName);
            
                //Once the file is copied use the SharePoint client side API to change the content type of the file
                ClientContext clientContext = new ClientContext(webURL);
                Microsoft.SharePoint.Client.List spList = clientContext.Web.Lists.GetByTitle(library);
                clientContext.Load(spList, s => s.ItemCount, s => s.BaseTemplate, s => s.BaseType, s => s.ContentTypes.Include(c => c.DocumentTemplate, c => c.Name, c => c.NewFormUrl, c => c.EditFormTemplateName, c => c.Id));
                clientContext.ExecuteQuery();
                ListItemCollection listItems = null;
                //Get the handle of the new file
                if (spList != null && spList.ItemCount > 0)
                {
                    Microsoft.SharePoint.Client.CamlQuery camlQuery = new CamlQuery();
                    camlQuery.ViewXml =
                        @"<View Scope='RecursiveAll'>  
                        <Query> 
                            <Where><And><Eq><FieldRef Name='FileLeafRef' /><Value Type='File'>" + uncPDFNewFileName + @"</Value></Eq><Eq><FieldRef Name='FSObjType' /><Value Type='Integer'>0</Value></Eq></And></Where> 
                        </Query> 
                    </View>";


                    listItems = spList.GetItems(camlQuery);
                    clientContext.Load(listItems);
                    clientContext.ExecuteQuery();
                }

                //Change the content time of the document to the content type of the template
                ContentType newDoc = null;
                foreach (ContentType item in spList.ContentTypes)
                {
                    if (string.Compare(item.DocumentTemplate, uncContentTemplateName, true) == 0)
                    {
                        newDoc = item;
                        if (listItems.Count > 0)
                        {
                            listItems[0]["ContentTypeId"] = item.Id;
                            listItems[0].Update();
                            clientContext.ExecuteQuery();
                        }
                    }
                }
                //Pass location of new file to Adobe
                AcroPDFLib.AdobeSPOpenDocuments pdfOpen = new AcroPDFLib.AdobeSPOpenDocuments();
                pdfOpen.EditDocument2(null, bstrNewFileFullPath);
                pdfOpen = null;

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
            return true;
           
        }
        
      /// <summary>
      /// Method for opendocuments.2 Copies the PDF template from bstrTemplateLocation to bstrDefaultSaveLocation
      /// so that it can be opened with Adobe
      /// </summary>
      /// <param name="pdisp"></param>
      /// <param name="bstrTemplateLocation"></param>
      /// <param name="bstrDefaultSaveLocation"></param>
      /// <returns></returns>
        [ComVisible(true)]
        [DispId(0x60020002)]
        bool IOpenDocument.CreateNewDocument2(object pdisp, string bstrTemplateLocation, string bstrDefaultSaveLocation)
        {

            //this is same code as CreateNewDocument1, but possible can be improved latter since the pdisp object is passed
            try
            {
                //find the UNC path to the template and saved template
                string uncPath = bstrDefaultSaveLocation.Replace('/', '\\').Remove(0, 5);
                string uncPDFTemplateFullPath = bstrTemplateLocation.Replace('/', '\\').Remove(0, 5);
                //Create new PDF file name for the saved file. Adobe PDF reader will not create a new file.
                string uncPDFNewFileName = System.IO.Path.GetFileNameWithoutExtension(uncPDFTemplateFullPath);
                string uncContentTemplateName = System.IO.Path.GetFileName(uncPDFTemplateFullPath);
                uncPDFNewFileName = string.Format(@"{0} {1} {2}.pdf", uncPDFNewFileName,
                    System.DateTime.Now.ToString("yyyymmddhhmm"), Environment.UserName);
                
                string uncPDFNewFileNameFullPath = string.Format(@"{0}\{1}", uncPath, uncPDFNewFileName);
                //Copy template to new file location
                CopyFile(uncPDFTemplateFullPath, uncPDFNewFileNameFullPath);
                string bstrNewFileFullPath = "http:" + uncPDFNewFileNameFullPath.Replace('\\', '/');
                string library = string.Empty;
                string bstrNewDocName = string.Empty;
                string webURL = ReturnWebSiteURL(bstrNewFileFullPath, out library, out bstrNewDocName);
                //Once the file is copied use the SharePoint client side API to change the content type of the file
                ClientContext clientContext = new ClientContext(webURL);
                Microsoft.SharePoint.Client.List spList = clientContext.Web.Lists.GetByTitle(library);
                clientContext.Load(spList, s => s.ItemCount, s => s.BaseTemplate, s => s.BaseType, s => s.ContentTypes.Include(c => c.DocumentTemplate, c => c.Name, c => c.NewFormUrl, c => c.EditFormTemplateName, c => c.Id));
                clientContext.ExecuteQuery();
                ListItemCollection listItems = null;
                if (spList != null && spList.ItemCount > 0)
                {
                    Microsoft.SharePoint.Client.CamlQuery camlQuery = new CamlQuery();
                    camlQuery.ViewXml =
                        @"<View Scope='RecursiveAll'>  
                        <Query> 
                            <Where><And><Eq><FieldRef Name='FileLeafRef' /><Value Type='File'>" + uncPDFNewFileName + @"</Value></Eq><Eq><FieldRef Name='FSObjType' /><Value Type='Integer'>0</Value></Eq></And></Where> 
                        </Query> 
                    </View>";


                    listItems = spList.GetItems(camlQuery);
                    clientContext.Load(listItems);
                    clientContext.ExecuteQuery();
                }
                //Change the content time of the document to the content type of the template
                ContentType newDoc = null;
                foreach (ContentType item in spList.ContentTypes)
                {
                    if (string.Compare(item.DocumentTemplate, uncContentTemplateName, true) == 0)
                    {
                        newDoc = item;
                        if (listItems.Count > 0)
                        {
                            listItems[0]["ContentTypeId"] = item.Id;
                            listItems[0].Update();
                            clientContext.ExecuteQuery();
                        }
                    }
                }
                //Open the new file in Adobe
                AcroPDFLib.AdobeSPOpenDocuments pdfOpen = new AcroPDFLib.AdobeSPOpenDocuments();
                pdfOpen.EditDocument2(pdisp, bstrNewFileFullPath);
                pdfOpen = null;

            }
            catch (Exception ex)
            {

                MessageBox.Show(ex.ToString());
            }         

            return true;
         }


        [ComVisible(true)]
        [DispId(0x60020003)]
        bool IOpenDocument.EditDocument(string bstrDocumentLocation, string varProgID)
        {
          
            try
            {
                AcroPDFLib.AdobeSPOpenDocuments pdfOpen = new AcroPDFLib.AdobeSPOpenDocuments();
                pdfOpen.EditDocument2(null, bstrDocumentLocation, varProgID);
                pdfOpen = null;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
            return true;

        }
        [ComVisible(true)]
        [DispId(0x60020004)]
        bool IOpenDocument.EditDocument2(object pdisp, string bstrDocumentLocation, string varProgID)
        {
            
                  
            try
            {
                AcroPDFLib.AdobeSPOpenDocuments pdfOpen = new AcroPDFLib.AdobeSPOpenDocuments();
                pdfOpen.EditDocument2(null, bstrDocumentLocation, varProgID);
                pdfOpen = null;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
            return true;
        }
      [ComVisible(true)]
         bool IOpenDocument.PromptedOnLastOpen()
        {
            return true;
        }
         [ComVisible(true)]
        bool IOpenDocument.ViewDocument(string bstrDocumentLocation, string varProgID)
        {
           
            return true;
        }

        [ComVisible(true)]
        bool IOpenDocument.ViewDocument2(object pdisp, string bstrDocumentLocation, string varProgID)
        {
            return true;
        }


      
        private void CopyFile(string source, string dest)
        {

            for (int i = 0; i < 10; i++ )
                try
                {

                    System.IO.File.Copy(source, dest, true);
                    break;
                }
                catch (Exception ex)
                {
                    //Try again let webdav wakeup
                    System.Threading.Thread.Sleep(2000);
                    if (i >= 9)
                        throw ex;


                }

        }

        private string ReturnWebSiteURL(string url, out string library, out string docName)
        {
            Uri uri = new Uri(url, UriKind.Absolute);
            Uri uri1 = new Uri(url, UriKind.Absolute);
            
            string host = uri.Host;
            string[] comp = uri.Segments;
           
          
            library = string.Empty;
            docName = comp[comp.GetUpperBound(0)];
            
            for(int i= comp.GetUpperBound(0)-1; i > 0;i--)
            {
                string temp = @"http://" + host + string.Join("", comp, 0, i);
                ClientContext client = null;
                try
                {
                    client = new ClientContext(temp);
                    client.ExecuteQuery();
                    client.Dispose();

                    library = Uri.UnescapeDataString(comp[i].TrimEnd('/')); ;
                 
                    return temp;
                }
                catch
                {
                   
                }
               
            }
            return string.Empty;
       }



        /// <summary>
        /// Called when derived class is registered as a COM server.
        /// User current user hive to register the COM server.
        /// </summary>
        [ComRegisterFunctionAttribute]
        public static void Register(Type t)
        {
            string guid = t.GUID.ToString("B").ToUpper();
     
            RegistryKey rkClass = Registry.CurrentUser.CreateSubKey(@"Software\Classes\CLSID\" + guid);
            RegistryKey rkCat = rkClass.CreateSubKey("Implemented Categories");
            RegistryKey rkInProc = rkClass.CreateSubKey("InprocServer32");
            rkInProc.SetValue(string.Empty, "mscoree.dll");

            rkInProc.SetValue("Assembly", t.Assembly.FullName);
            string versionString = null;
            try
            {
                Regex regexObj = new Regex("[\\w.]*, Version=(?<Version>[\\d.]*), Culture=(?<Culture>\\w*), PublicKeyToken=(?<PublicKeyToken>\\w*)");
                versionString = regexObj.Match(t.Assembly.FullName).Groups[1].Value;
            }
            catch (ArgumentException ex)
            {
                throw ex;
            }

            rkInProc.SetValue("Class", t.FullName);
            rkInProc.SetValue("CodeBase", t.Assembly.Location);
            rkInProc.SetValue("RuntimeVersion", t.Assembly.ImageRuntimeVersion);
            rkInProc.SetValue("ThreadingModel", @"Both");
            RegistryKey rkVersion = rkInProc.CreateSubKey(versionString);
            rkVersion.SetValue("Assembly", t.Assembly.FullName);
            rkVersion.SetValue("Class", t.FullName);
            rkVersion.SetValue("CodeBase", t.Assembly.Location);
            rkVersion.SetValue("RuntimeVersion", t.Assembly.ImageRuntimeVersion);
            rkVersion.Close();
            rkInProc.Close();

            object[] attributes = t.GetCustomAttributes(typeof(ComVisibleAttribute), true);
            string strFullProgID = string.Empty;
            string strTitle = string.Empty;

            if (attributes.Length > 0)
            {
                attributes = t.GetCustomAttributes(typeof(ProgIdAttribute), true);
                if (attributes.Length > 0)
                    strFullProgID = string.Format(@"Software\Classes\{0}", (attributes[0] as ProgIdAttribute).Value);
                attributes = t.GetCustomAttributes(typeof(AssemblyTitleAttribute), true);
                if (attributes.Length > 0)
                    strTitle = string.Format(@"{0}", (attributes[0] as AssemblyTitleAttribute).Title);
            }

            if (strFullProgID != string.Empty)
            {

                RegistryKey rkProgID = Registry.CurrentUser.CreateSubKey(strFullProgID);
                rkProgID.SetValue(string.Empty, t.FullName, RegistryValueKind.String);
                rkProgID.CreateSubKey("CLSID").SetValue(string.Empty, guid, RegistryValueKind.String);
            }

            string name = t.FullName;
           
            if (strTitle != string.Empty)
            {
                rkClass.SetValue(null, strTitle);
            }
            else
                rkClass.SetValue(null, name);

            //Registry.CurrentUser.CreateSubKey(@"SOFTWARE\Microsoft\Internet Explorer\Toolbar").SetValue(guid, false);
            Registry.CurrentUser.CreateSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Ext\PreApproved").SetValue(guid, string.Empty);


            rkCat.Close();
            rkClass.Close();

        }
        /// <summary>
        /// Called when derived class is unregistered as a COM server.
        /// </summary>
        [ComUnregisterFunctionAttribute]
        public static void Unregister(Type t)
        {
            string guid = t.GUID.ToString("B");
        
          
            //Registry.CurrentUser.CreateSubKey(@"SOFTWARE\Microsoft\Internet Explorer\Toolbar").DeleteValue(guid, false);
            Registry.CurrentUser.CreateSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Ext\PreApproved").DeleteValue(guid, false);
            if (Registry.CurrentUser.OpenSubKey(@"Software\Classes\CLSID\" + guid) != null)
            {
                Registry.CurrentUser.CreateSubKey(@"Software\Classes\CLSID\").DeleteSubKeyTree(guid);
            }


            object[] attributes = t.GetCustomAttributes(typeof(ComVisibleAttribute), true);
            string strProgID = string.Empty;
            string strFullProgID = string.Empty;
            if (attributes.Length > 0)
            {
                attributes = t.GetCustomAttributes(typeof(ProgIdAttribute), true);
                strProgID = (attributes[0] as ProgIdAttribute).Value.ToString();
                strFullProgID = string.Format(@"Software\Classes\{0}", strProgID);
            }

            if (strProgID != string.Empty)
            {


                if (Registry.CurrentUser.OpenSubKey(strFullProgID) != null)
                {

                    Registry.CurrentUser.CreateSubKey(@"Software\Classes\").DeleteSubKeyTree(strProgID);
                }


            }

        }

        #endregion
    }



}
